package com.xuelang.pipeline.infrastructure.filter;

import com.aliyun.gts.sso.boot.SFSSOLoginProperties;
import com.aliyun.gts.sso.context.SFLoginAccountContext;
import com.aliyun.gts.sso.domain.LoginAccount;
import com.xuelang.hiforce.base.cache.client.service.DistributeCacheManager;
import com.xuelang.hiforce.base.tracing.TracingConstants;
import com.xuelang.jadp.client.sys.dto.UserInfoDTO;
import com.xuelang.sys.app.sys.UserManagerService;
import com.xuelang.sys.app.sys.utils.JadpTokenUtil;
import com.xuelang.sys.app.sys.utils.PathMatcher;
import com.xuelang.sys.app.sys.utils.UserAgent;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * SF 上航 客户端登录拦截器
 */
@Component("SFAuthenticationFilter")
@Slf4j
public class SFAuthenticationFilter implements Filter {

    @Autowired(required = false)
    private SFSSOLoginProperties sfSSOLoginProperties;

    @Autowired
    private UserManagerService userManagerService;

    @Autowired
    private DistributeCacheManager distributeCacheManager;

    private static final String LOCK_KEY = "userCreationLock"; // 锁的键

    private static final Long LOCK_TIMEOUT = 10L; // 锁的超时时间（单位：秒）

    private static PathMatcher pathMatcher = null;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    @SneakyThrows
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        String requestURI = req.getRequestURI();

        try {
            MDC.clear();
        } catch (Exception e) {
        }

        if (sfSSOLoginProperties == null || !sfSSOLoginProperties.isEnabled()) {
            // 未开启单点登录
            chain.doFilter(request, response);
            return;
        }


        if (pathMatcher == null) {
            synchronized (SFAuthenticationFilter.class) {
                if (pathMatcher == null) {
                    Set<String> ignoreUriPatterns = sfSSOLoginProperties.getIgnoreUriPatterns();
                    if (ignoreUriPatterns == null) {
                        ignoreUriPatterns = new HashSet<>();
                    }
                    pathMatcher = new PathMatcher(new ArrayList<>(ignoreUriPatterns));
                }
            }
        }


        if (pathMatcher.match(requestURI) || req.getMethod().equals(HttpMethod.OPTIONS.toString())) {
            chain.doFilter(request, response);
            return;
        }

        log.debug("sf request path:{} auth interface", req.getRequestURI());
        authFitler(req, resp, chain);

    }


    private void authFitler(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws Exception {
        LoginAccount loginAccount = SFLoginAccountContext.getLoginAccount();
        if (loginAccount == null) {
            loginAccount = new LoginAccount();
            loginAccount.setId(1l);
            loginAccount.setGuid("testfornull");
            loginAccount.setUserCode("testfornull");
            loginAccount.setUserName("当登录用户为null时的默认测试用户");
        }
        setUserContent(loginAccount, null);
        checkAndSaveFirstTimeUser(loginAccount);
        chain.doFilter(request, response);
    }

    /**
     * 设置用户的上下文信息
     *
     * @param accessToken
     */
    private void setUserContent(LoginAccount userInfoDTO, String accessToken) throws Exception {
        // 在调用链及MDC中放入当前登录用户的信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        putLoginMDC(userInfoDTO, request);
        putLoginTracing();
    }

    /**
     * 在MDC中放入当前的登录信息
     *
     * @param userInfo
     * @param request
     */
    private void putLoginMDC(LoginAccount userInfo, HttpServletRequest request) {
        log.info("[ Put Login MDC ]: userInfo is " + userInfo);
        MDC.put(TracingConstants.USER_ID, userInfo.getGuid());
        MDC.put(TracingConstants.USER_ACCOUNT, userInfo.getUserCode());
        MDC.put(TracingConstants.USER_NAME, userInfo.getUserName());
        log.info("[ Put Login MDC ]: user id in mdc is " + MDC.get(TracingConstants.USER_ID));

        MDC.put(TracingConstants.REQUEST_IP, getIpAddress(request));
        MDC.put(TracingConstants.REQUEST_PATH, request.getServletPath());
        MDC.put(TracingConstants.REQUEST_METHOD, request.getMethod());
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        if (userAgent != null) {
            MDC.put(TracingConstants.REQUEST_OS, userAgent.getOperatingSystem() != null ? userAgent.getOperatingSystem() : null);
            MDC.put(TracingConstants.REQUEST_BROWSER, userAgent.getBrowser() != null ? userAgent.getBrowser() : null);
        }
    }

    /**
     * 在调用链中放入当前登录用户的信息
     *
     * @throws Exception
     */
    private void putLoginTracing() {
        TraceContext.putCorrelation(TracingConstants.USER_ID, MDC.get(TracingConstants.USER_ID));
        TraceContext.putCorrelation(TracingConstants.USER_ACCOUNT, MDC.get(TracingConstants.USER_ACCOUNT));

        TraceContext.putCorrelation(TracingConstants.REQUEST_IP, MDC.get(TracingConstants.REQUEST_IP));
        TraceContext.putCorrelation(TracingConstants.REQUEST_PATH, MDC.get(TracingConstants.REQUEST_PATH));
        TraceContext.putCorrelation(TracingConstants.REQUEST_METHOD, MDC.get(TracingConstants.REQUEST_METHOD));

        TraceContext.putCorrelation(TracingConstants.REQUEST_OS, MDC.get(TracingConstants.REQUEST_OS));
        TraceContext.putCorrelation(TracingConstants.REQUEST_BROWSER, MDC.get(TracingConstants.REQUEST_BROWSER));
        TraceContext.putCorrelation(TracingConstants.APP_KEY, MDC.get(TracingConstants.APP_KEY));
    }


    /**
     * 获取用户真实IP地址
     */
    public static String getIpAddress(HttpServletRequest request) {
        // k8s 下优先： x-Original-Forwarded-For
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
        }


        String ip = request.getHeader("x-Original-Forwarded-For");
        String unknown = "unknown";
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("x-forwarded-for");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip != null && ip.length() != 0 && !unknown.equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if (ip.contains(",")) {
                ip = ip.split(",")[0];
            }
        }
        return ip;
    }

    /**
     * 检查是否是初次登录系统的用户，若是，则在系统新增用户；
     * @param userInfoDTO
     */
    private void checkAndSaveFirstTimeUser(LoginAccount userInfoDTO) {
        // 尝试获取分布式锁
        boolean lockAcquired = distributeCacheManager.setIfAbsent(LOCK_KEY, "locked", LOCK_TIMEOUT, TimeUnit.SECONDS);
        if (!lockAcquired) {
            return;
        }

        try {
            // 先检查用户是否存在于数据库中
            UserInfoDTO found = userManagerService.getUserById(userInfoDTO.getGuid());

            // 若存在 则直接结束
            if (Objects.nonNull(found)) {
                return;
            }

            // 若不存在，则新增用户
            UserInfoDTO newUser = new UserInfoDTO();
            newUser.setId(userInfoDTO.getGuid());
            newUser.setAccount(userInfoDTO.getUserCode());
            newUser.setRealName(userInfoDTO.getUserName());
            newUser.setMobile(userInfoDTO.getPhone());
            userManagerService.saveUser(newUser);
        } finally {
            // 无论成功与否，都要释放锁
            distributeCacheManager.del(LOCK_KEY);
        }
    }

}
